<?php
/**
 * Created by PhpStorm.
 * User: songdewei
 * Date: 2017/8/14
 * Time: 下午12:23
 */
namespace App\WeChat\WxPay;
use App\WeChat\WxPay\Order\WxPayBizPayUrl;
use App\WeChat\WxPay\Order\WxPayCloseOrder;
use App\WeChat\WxPay\Order\WxPayDownloadBillOrder;
use App\WeChat\WxPay\Order\WxPayMicroPayOrder;
use App\WeChat\WxPay\Order\WxPayQueryOrder;
use App\WeChat\WxPay\Order\WxPayRefundOrder;
use App\WeChat\WxPay\Order\WxPayRefundQueryOrder;
use App\WeChat\WxPay\Order\WxPayReverseOrder;
use App\WeChat\WxPay\Order\WxPayShortUrl;
use App\WeChat\WxPay\Order\WxPayTransferOrder;
use App\WeChat\WxPay\Order\WxPayUnifiedOrder;
use App\WeChat\WxPay\Response\WxPayCloseOrderResponse;
use App\WeChat\WxPay\Response\WxPayQueryOrderResponse;
use App\WeChat\WxPay\Response\WxPayRefundOrderResponse;
use App\WeChat\WxPay\Response\WxPayRefundQueryResponse;
use App\WeChat\WxPay\Response\WxPayReverseResponse;
use App\WeChat\WxPay\Response\WxPayUnifiedOrderResponse;

class WxPayApi{
    /**
     * 以post方式提交xml到对应的接口url
     * @param string $xml 需要post的xml数据
     * @param string $url url
     * @param bool $useCert 是否需要证书，默认不需要
     * @param int $second url执行超时时间，默认30s
     * @return mixed
     * @throws \Exception
     */
    public static function postXmlCurl($xml, $url, $useCert = false, $second = 30){
        $ch = curl_init();
        //设置超时
        curl_setopt($ch, CURLOPT_TIMEOUT, $second);

        //如果有配置代理这里就设置代理
        /*
        if(WxPayConfig::CURL_PROXY_HOST != "0.0.0.0"
            && WxPayConfig::CURL_PROXY_PORT != 0){
            curl_setopt($ch,CURLOPT_PROXY, WxPayConfig::CURL_PROXY_HOST);
            curl_setopt($ch,CURLOPT_PROXYPORT, WxPayConfig::CURL_PROXY_PORT);
        }
        */
        curl_setopt($ch,CURLOPT_URL, $url);
        curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,TRUE);
        curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,2);//严格校验
        //设置header
        curl_setopt($ch, CURLOPT_HEADER, FALSE);
        //要求结果为字符串且输出到屏幕上
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);

        if($useCert == true){
            //设置证书
            //使用证书：cert 与 key 分别属于两个.pem文件
            curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLCERT, base_path('cert/wxpay/apiclient_cert.pem'));
            curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM');
            curl_setopt($ch,CURLOPT_SSLKEY, base_path('cert/wxpay/apiclient_key.pem'));
        }
        //post提交方式
        curl_setopt($ch, CURLOPT_POST, TRUE);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
        //运行curl
        $data = curl_exec($ch);
        //返回结果
        if($data){
            curl_close($ch);
            return $data;
        } else {
            $error = curl_errno($ch);
            curl_close($ch);
            throw new \Exception("curl出错，错误码:$error");
        }
    }


    /**
     * 统一下单接口
     * @param WxPayUnifiedOrder $order
     * @param int $timeout
     * @return WxPayUnifiedOrderResponse
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \Exception
     */
    public static function unifiedOrder(WxPayUnifiedOrder $order, $timeout = 20){
        $url = "https://api.mch.weixin.qq.com/pay/unifiedorder";
        if(!$order->getOutTradeNo()){
            throw new \Exception('缺少统一支付接口必填参数out_trade_no！', 1);
        }
        if (!$order->getBody()){
            throw new \Exception('缺少统一支付接口必填参数body！',2);
        }

        if (!$order->getTotalFee()){
            throw new \Exception('缺少统一支付接口必填参数total_fee！',3);
        }

        if ($order->getTradeType() == 'JSAPI' && !$order->getOpenid()){
            throw new \Exception('统一支付接口中，缺少必填参数openid！',4);
        }

        if ($order->getTradeType() == 'NATIVE' && !$order->getProductId()){
            throw new \Exception('统一支付接口中，缺少必填参数product_id！',5);
        }

        if (!$order->getNotifyUrl()) {
            throw new \Exception('统一支付接口中，缺少必填参数notify_url！',6);
        }

        if (!$order->getTradeType()){
            $order->setTradeType();
        }

        if (!$order->isSetAppid()) {
            $order->setAppid();
        }

        if (!$order->isSetMchId()) {
            $order->setMchId();
        }

        if (!$order->getMchKey()) {
            $order->setMchKey();
        }

        $order->setNonceStr();
        $order->setSpbillCreateIp();
        $order->setSign();
        $xml = $order->toXml();
        $res = self::postXmlCurl($xml, $url, false, $timeout);

        return new WxPayUnifiedOrderResponse(self::xml2array($res));
    }

    /**
     * 查询订单，out_trade_no、transaction_id至少填一个
     * @param WxPayQueryOrder $order
     * @param int $timeout
     * @return WxPayQueryOrderResponse
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \Exception
     */
    public static function query(WxPayQueryOrder $order, $timeout=20){
        $url = "https://api.mch.weixin.qq.com/pay/orderquery";
        if (!$order->isSetOutTradeNo() && !$order->isSetTransactionId()){
            throw new \Exception('订单查询接口中，out_trade_no、transaction_id至少填一个！');
        }

        if (!$order->isSetAppid()) {
            $order->setAppid();
        }

        if (!$order->isSetMchId()){
            $order->setMchId();
        }

        if (!$order->isSetNonceStr()) {
            $order->setNonceStr();
        }

        $order->setSign();
        $xml = $order->toXml();
        $res = self::postXmlCurl($xml, $url, false, $timeout);
        return new WxPayQueryOrderResponse(self::xml2array($res));
    }

    /**
     * 撤销订单API接口，参数out_trade_no和transaction_id必须填写一个
     * @param WxPayReverseOrder $order
     * @param int $timeOut
     * @return WxPayReverseResponse
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \Exception
     */
    public static function reverse(WxPayReverseOrder $order, $timeOut = 20){
        $url = "https://api.mch.weixin.qq.com/secapi/pay/reverse";
        //检测必填参数
        if(!$order->getOutTradeNo() && !$order->getTransactionId()) {
            throw new \Exception("撤销订单API接口中，参数out_trade_no和transaction_id必须填写一个！");
        }

        if (!$order->isSetAppid()){
            $order->setAppid();
        }

        if (!$order->isSetMchId()){
            $order->setMchId();
        }

        if (!$order->getNonceStr()){
            $order->setNonceStr();
        }

        $order->setSign();//签名
        $xml = $order->toXml();
        $res = self::postXmlCurl($xml, $url, true, $timeOut);

        return new WxPayReverseResponse(self::xml2array($res));
    }

    /**
     * 关闭订单，out_trade_no必填
     * @param WxPayCloseOrder $order
     * @param int $timeOut
     * @return WxPayCloseOrderResponse
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \Exception
     */
    public static function close(WxPayCloseOrder $order, $timeOut = 20){
        $url = "https://api.mch.weixin.qq.com/pay/closeorder";
        //检测必填参数
        if(!$order->getOutTradeNo()) {
            throw new \Exception("订单查询接口中，out_trade_no必填！", 1);
        }

        if (!$order->isSetAppid()){
            $order->setAppid();
        }

        if (!$order->isSetMchId()){
            $order->setMchId();
        }

        if (!$order->getNonceStr()) {
            $order->setNonceStr();
        }

        $order->setSign();//签名
        $xml = $order->toXml();
        $res = self::postXmlCurl($xml, $url, true, $timeOut);

        return new WxPayCloseOrderResponse(self::xml2array($res));
    }

    /**
     * out_refund_no、total_fee、refund_fee、op_user_id为必填参数
     * appid、mchid、spbill_create_ip、nonce_str不需要填入
     * @param WxPayRefundOrder $order
     * @param int $timeOut
     * @return WxPayRefundOrderResponse
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \Exception
     */
    public static function refund(WxPayRefundOrder $order, $timeOut = 20){
        $url = "https://api.mch.weixin.qq.com/secapi/pay/refund";
        //检测必填参数
        if(!$order->getOutTradeNo() && !$order->getTransactionId()) {
            throw new \Exception("退款申请接口中，out_trade_no、transaction_id至少填一个！", 1);
        }

        if(!$order->getOutRefundNo()){
            throw new \Exception("退款申请接口中，缺少必填参数out_refund_no！", 2);
        }

        if(!$order->getTotalFee()){
            throw new \Exception("退款申请接口中，缺少必填参数total_fee！", 3);
        }

        if(!$order->getRefundFee()){
            throw new \Exception("退款申请接口中，缺少必填参数refund_fee！", 4);
        }

        if (!$order->isSetAppid()){
            $order->setAppid();
        }

        if (!$order->isSetMchId()){
            $order->setMchId();
        }

        if (!$order->getNonceStr()){
            $order->setNonceStr();
        }

        $order->setSign();//签名
        $xml = $order->toXml();
        $res = self::postXmlCurl($xml, $url, true, $timeOut);

        return new WxPayRefundOrderResponse(self::xml2array($res));
    }

    /**
     * 查询退款
     * 提交退款申请后，通过调用该接口查询退款状态。退款有一定延时，
     * 用零钱支付的退款20分钟内到账，银行卡支付的退款3个工作日后重新查询退款状态。
     * WxPayRefundQuery中out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个
     * @param WxPayRefundQueryOrder $order
     * @param int $timeOut
     * @return WxPayRefundQueryResponse
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \Exception
     */
    public static function refundQuery(WxPayRefundQueryOrder $order, $timeOut = 20){
        $url = "https://api.mch.weixin.qq.com/pay/refundquery";
        //检测必填参数
        if(!$order->getRefundId() && !$order->getOutRefundNo() && !$order->getOutTradeNo() && !$order->getTransactionId()) {
            throw new \Exception("退款查询接口中，refund_id,out_refund_no、out_trade_no、transaction_id四个参数必填一个！", 1);
        }

        if (!$order->isSetAppid()){
            $order->setAppid();
        }

        if (!$order->isSetMchId()){
            $order->setMchId();
        }

        if (!$order->getNonceStr()){
            $order->setNonceStr();
        }

        $order->setSign();//签名
        $xml = $order->toXml();
        $res = self::postXmlCurl($xml, $url, true, $timeOut);

        return new WxPayRefundQueryResponse(self::xml2array($res));
    }

    /**
     * 下载对账单，bill_date为必填参数
     * appid、mchid、spbill_create_ip、nonce_str不需要填入
     * @param WxPayDownloadBillOrder $order
     * @param int $timeOut
     * @return array|mixed
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \Exception
     */
    public static function downloadBill(WxPayDownloadBillOrder $order, $timeOut = 20){
        $url = "https://api.mch.weixin.qq.com/pay/downloadbill";
        //检测必填参数
        if(!$order->getBillDate()) {
            throw new \Exception("对账单接口中，缺少必填参数bill_date！", 1);
        }

        if (!$order->isSetAppid()){
            $order->setAppid();
        }

        if (!$order->isSetMchId()){
            $order->setMchId();
        }

        if ($order->getNonceStr()){
            $order->setNonceStr();
        }

        $order->setSign();//签名
        $xml = $order->toXml();
        $res = self::postXmlCurl($xml, $url, true, $timeOut);
        return self::xml2array($res);
    }

    /**
     * 提交被扫支付API
     * 收银员使用扫码设备读取微信用户刷卡授权码以后，二维码或条码信息传送至商户收银台，
     * 由商户收银台或者商户后台调用该接口发起支付。
     * WxPayWxPayMicroPay中body、out_trade_no、total_fee、auth_code参数必填
     * appid、mchid、spbill_create_ip、nonce_str不需要填入
     * @param WxPayMicroPayOrder $order
     * @param int $timeOut
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \Exception
     */
    public static function micropay(WxPayMicroPayOrder $order, $timeOut = 10){
        $url = "https://api.mch.weixin.qq.com/pay/micropay";
        //检测必填参数
        if(!$order->getBody()) {
            throw new \Exception("提交被扫支付API接口中，缺少必填参数body！", 1);
        }

        if(!$order->getOutTradeNo()) {
            throw new \Exception("提交被扫支付API接口中，缺少必填参数out_trade_no！", 2);
        }

        if(!$order->getTotalFee()) {
            throw new \Exception("提交被扫支付API接口中，缺少必填参数total_fee！", 3);
        }

        if(!$order->getAuthCode()) {
            throw new \Exception("提交被扫支付API接口中，缺少必填参数auth_code！", 4);
        }

        if (!$order->isSetAppid()){
            $order->setAppid();
        }

        if (!$order->isSetMchId()){
            $order->setMchId();
        }

        if (!$order->getNonceStr()){
            $order->setNonceStr();
        }

        if (!$order->getSpbillCreateIp()) {
            $order->setSpbillCreateIp();
        }

        $order->setSign();//签名
        $xml = $order->toXml();
        $res = self::postXmlCurl($xml, $url, true, $timeOut);

        self::xml2array($res);
    }

    /**
     * 企业付款给用户
     * @param WxPayTransferOrder $order
     * @param int $timeout
     * @return array|mixed
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \Exception
     */
    public static function transfer(WxPayTransferOrder $order, $timeout = 10){
        $url = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
        if (!$order->getPartnerTradeNo()){
            throw new \Exception("企业付款接口中，缺少必填参数partner_trade_no！", 1);
        }

        if (!$order->getAmount()){
            throw new \Exception("企业付款接口中，缺少必填参数amount！", 2);
        }

        if (!$order->getDesc()){
            throw new \Exception("企业付款接口中，缺少必填参数desc！", 3);
        }

        if (!$order->getOpenid()){
            throw new \Exception("企业付款接口中，缺少必填参数openid！", 4);
        }

        if (!$order->getMchAppid()){
            $order->setMchAppid();
        }

        if ($order->getMchid()){
            $order->setMchid();
        }

        if (!$order->getNonceStr()){
            $order->setNonceStr();
        }

        $order->setSign();
        $xml = $order->toXml();
        $res = self::postXmlCurl($xml, $url, true, $timeout);

        return self::xml2array($res);
    }

    /**
     * 转换短链接
     * 该接口主要用于扫码原生支付模式一中的二维码链接转成短链接(weixin://wxpay/s/XXXXXX)，
     * 减小二维码数据量，提升扫描速度和精确度。
     * appid、mchid、spbill_create_ip、nonce_str不需要填入
     * @param WxPayShortUrl $order
     * @param int $timeOut
     * @return array|mixed
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \Exception
     */
    public static function shorturl(WxPayShortUrl $order, $timeOut = 20){
        $url = "https://api.mch.weixin.qq.com/tools/shorturl";
        //检测必填参数
        if(!$order->getLongUrl()) {
            throw new \Exception("需要转换的URL，签名用原串，传输需URL encode！", 1);
        }

        if (!$order->isSetAppid()){
            $order->setAppid();
        }

        if (!$order->isSetMchId()){
            $order->setMchId();
        }

        if (!$order->getNonceStr()){
            $order->setNonceStr();
        }

        $order->setSign();//签名
        $xml = $order->toXml();
        $res = self::postXmlCurl($xml, $url, true, $timeOut);

        return self::xml2array($res);
    }

    /**
     * 获取原生支付URL
     * @param WxPayBizPayUrl $order
     * @param int $timeout
     * @return string
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @throws \Exception
     */
    public static function nativePay(WxPayBizPayUrl $order, $timeout = 20){
        if (!$order->getProductId()) {
            throw new \Exception("付款接口中，缺少必填参数product_id！", 1);
        }

        if (!$order->isSetAppid()){
            $order->setAppid();
        }

        if (!$order->isSetMchId()){
            $order->setMchId();
        }

        if (!$order->getTimestamp()){
            $order->setTimetamp();
        }

        if (!$order->getNonceStr()){
            $order->setNonceStr();
        }

        $order->setSign();
        return $order->getBizUrl();
    }

    /**
     * 将xml转为array
     * @param string $xml
     * @return array|mixed
     */
    public static function xml2array($xml){
        //将XML转为array
        //禁止引用外部xml实体
        libxml_disable_entity_loader(true);
        $array = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
        return $array;
    }
}
